/*
 * Copyright (c) 2010-2013 Ruben Chakhmakhchyan. All rights reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Eclipse Public License
 * Version 1.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://opensource.org/licenses/EPL-1.0 and read it before using this file.
 */

#include "plugin_model_impl.h"
#include "image_resize.h"
#include "jpeg_encoder.h"
#include "base64.h"
#include "zlib_encoder.h"
#include "dib.h"
#include <math.h>
#include "math2.h"
#include "strings.h"

#ifdef _DEBUG
#include <stdlib.h>
#include <stdio.h>
#endif


_PluginModel::Image::Image( PluginModel* model, Pixels& pixels )
    : 
    m_storedData( NULL ), 
    m_storedDataLength( 0 ), 
    m_thumbnailUrl( NULL ),
    m_storing( false ),
    m_extracting( false ),
    m_hLockEvent( NULL ),
    m_isLockInternal( true ),
    m_packed( false ),
    m_isActive( false ),
    m_locked( false ),
    m_isLockExternal( false ),
    m_index( -1 ),
    m_model( model ),
    m_storedDataFormat( StoreFormat::Zlib ),
    m_tag( NULL )
    //m_lockCount( 0 )
{
    m_pixels = pixels;

    // create lock event
    if ( !( m_hLockEvent = ::CreateEvent( NULL, TRUE, FALSE, NULL ) ) )
    {
        throw "Cannot create lock event!";
    }
}

_PluginModel::Image::~Image()
{
    lock();

    if ( m_pixels.pixels ) ::free( m_pixels.pixels );
    if ( m_storedData ) delete m_storedData;

    if ( m_thumbnailUrl ) delete m_thumbnailUrl;

    unlock();

    ::CloseHandle( m_hLockEvent );
    m_hLockEvent = NULL;
}

void _PluginModel::Image::lock()
{
    doLock();
    m_isLockExternal = true;
}

void _PluginModel::Image::doLock()
{
    if ( m_hLockEvent == NULL ) return;

    //m_lockCount++;

    if ( m_locked )
    {
#ifdef _DEBUG
        printf( "wait lock: %i\n", m_index );
#endif

        bool proceed = true;
        HANDLE event = ( HANDLE ) m_hLockEvent;

        while( proceed )
        {
            DWORD waitResult = ::WaitForSingleObject( event, 50L );

            switch ( waitResult )
            {
            case WAIT_OBJECT_0:
                {
                    proceed = false;
                    ::ResetEvent( event );
                }
                break;

            case WAIT_TIMEOUT: break;
            }
        }
    }

#ifdef _TEST
    printf( "lock: %i\n", m_index );
#endif

    m_locked = true;
}

bool _PluginModel::Image::isLocked()
{
    return m_locked;
}

void _PluginModel::Image::unlock()
{
    if ( m_hLockEvent == NULL ) return;

    if ( m_locked )
    {
        /*if ( m_lockCount > 0 )
        {
        m_lockCount--;
        }

        if ( m_lockCount == 0 )
        {*/
#ifdef _TEST
        printf( "unlock: %i\n", m_index );
#endif

        if ( !::SetEvent( m_hLockEvent ) )
            throw "Cannot set the lock event!";

        m_locked = false;
        m_isLockExternal = false;
        //}
    }
}

void _PluginModel::Image::store()
{
    if ( !m_isLockExternal )
        doLock();

    if ( !isStored() )
    {
#ifdef _TEST
        printf( "-store: %i ", m_index );

        uint_t t = GetTickCount();
#endif

        m_storing = true;

        byte_t* data = NULL;
        size_t dataLength = 0;

        switch ( m_storedDataFormat )
        {
        case StoreFormat::Zlib:
            {
                uint_t bufferLength = m_pixels.length;
                byte_t* buffer = ( byte_t* ) ::calloc( 1, bufferLength );
                //::memset( buffer, 0, bufferLength );

                ulong_t destLen = bufferLength;

                if ( !ZlibEncoder::Compress2( buffer, &destLen, m_pixels.pixels, m_pixels.length, m_model->getAutoCompressionLevel() ) )
                    throw "Compression failed.";

                data = ( byte_t* ) ::malloc( destLen );
                ::memcpy( data, buffer, destLen );

                dataLength = destLen;

                delete buffer;
            }
            break;

        case DocumentFormat::Jpeg:
            {
                PBITMAPINFOHEADER bih = ( PBITMAPINFOHEADER ) m_pixels.pixels;

                JpegEncoder encoder;

                dataLength = encoder.encode( &data, m_pixels, m_model->getImageQuality() );
            }
            break;
        }

        m_storedData = data;
        m_storedDataLength = dataLength;

#ifdef _TEST

        t = GetTickCount() - t;
        printf( " (%i)\n", t );

#endif
        m_storing = false;
    }

    if ( !m_isLockExternal )
        unlock();
}

void _PluginModel::Image::lighten()
{
    if ( !m_isActive )
    {
        if ( !m_isLockExternal )
            doLock();

        if ( m_storedData && m_pixels.pixels )
        {
#ifdef _TEST
            printf( "-lighten: %i\n", m_index );
#endif

            ::free( m_pixels.pixels );
            m_pixels.pixels = NULL;

            //m_dataLength = 0;
        }

        if ( !m_isLockExternal )
            unlock();
    }
}

void _PluginModel::Image::extract()
{
    if ( !m_isLockExternal )
        doLock();

    if ( !isExtracted() )
    {
#ifdef _TEST
        printf( "-extract: %i\n", m_index );
#endif

        m_extracting = true;

        byte_t* data = NULL;

        switch ( m_storedDataFormat )
        {
        case StoreFormat::Zlib:
            {
                data = ( byte_t* ) ::malloc( m_pixels.length );

                ulong_t dataLength = ( ulong_t ) m_pixels.length;

                if ( !ZlibEncoder::Uncompress( data, &dataLength, m_storedData, m_storedDataLength ) )
                    throw "Decompression failed.";

                if ( dataLength != ( ulong_t ) m_pixels.length )
                {
                    ::free( data );
                    return;
                }
            }

            break;

        case StoreFormat::Jpeg:
            {
                JpegEncoder encoder;

                size_t dataLength = encoder.decode( &data, m_storedData, m_storedDataLength, m_pixels.width, m_pixels.height, m_pixels.bitsPerPixel, -1, m_pixels.paletteInverted, m_pixels.resolution );
            }

            break;
        }

        m_pixels.pixels = data;

        m_extracting = false;
    }

    if ( !m_isLockExternal )
        unlock();
}

void _PluginModel::Image::pack( _PluginModel::Image::PackingAction action )
{
    CRITICAL_SECTION section = { 0 };

    ::InitializeCriticalSection( &section );
    ::EnterCriticalSection( &section );

    switch ( action )
    {
    case PackingAction::Store: this->store(); break;
    case PackingAction::Lighten: this->lighten(); break;
    case PackingAction::Extract: this->extract(); break;
    }

    ::LeaveCriticalSection( &section );
}

const char* _PluginModel::Image::getThumbnailUrl()
{
    if ( m_thumbnailUrl ) return m_thumbnailUrl;

    //lock();

    uint_t initialWidth = m_pixels.width, initialHeight = m_pixels.height;
    uint_t width = 0, height = 0;
    uint_t thumbnailSize = this->m_model->getImageThumbnailSize();

    if ( initialWidth > initialHeight )
    {
        width = thumbnailSize;
        height = ( uint_t ) ::lround( width * initialHeight / ( double ) initialWidth );
    }
    else
    {
        height = thumbnailSize;
        width = ( uint_t ) ::lround( height * initialWidth / ( double ) initialHeight );
    }
    double scaleFactor = width / ( double ) initialWidth;

    byte_t* pdata = NULL;
    byte_t* resizedData = NULL;

    //size_t resizedDataLength = ::resizeImageBilinear( &resizedData, width, height, ( byte_t* ) m_pixels.pixels );
    size_t resizedDataLength = ::resizeImageNearest( &resizedData, 0, 0, width, height, scaleFactor, &m_pixels );

    // convert to jpeg

    JpegEncoder encoder;

    Pixels p = Pixels( m_pixels );
    p.width = width;
    p.height = height;
    p.bitsPerChannel = m_pixels.bitsPerChannel == 1 ? 8 : m_pixels.bitsPerChannel;
    p.bitsPerPixel = m_pixels.bitsPerPixel == 1 ? 8 : m_pixels.bitsPerPixel;
    p.resolution = 72;
    p.pixels = resizedData;
    p.length = resizedDataLength;
    p.rowStride = ( ( width * p.bitsPerPixel + 7 ) & ( ~7 ) ) >> 3;
    p.rowAlignment = 8;

    size_t length = encoder.encode( &pdata, p, 70 );

    ::free( resizedData );

    //unlock();

    char* dataPrefix = "data:image/jpeg;base64,";
    uint_t dataPrefixLen = String::GetLength( dataPrefix );

    uint_t base64Len = base64Length( length );

    uint_t bufferLength = dataPrefixLen + base64Len + 1;

    char* buffer = new char[ bufferLength ];
    ::memset( buffer, 0, bufferLength );
    ::memcpy( buffer, dataPrefix, dataPrefixLen );

    bool result = base64Encode( ( char* ) buffer + dataPrefixLen, ( byte_t* ) pdata, length );

    ::free( pdata );

    m_thumbnailUrl = result ? buffer : NULL;

    return buffer;
}

byte_t* _PluginModel::Image::getBits() const
{
    return m_pixels.pixels;
}

PixelType _PluginModel::Image::getPixelType() const
{
    PixelType value = PixelType::Default;

    switch ( m_pixels.bitsPerPixel )
    {
    case 24: value = PixelType::Rgb; break;
    case 8: value = PixelType::Gray; break;
    case 1: value = PixelType::Bw; break;
    }
    return value;
}

bool _PluginModel::Image::getImageData( List<byte_t>& list, int x, int y, int width, int height )
{
    Pixels& p = m_pixels;

    if ( x >= 0 && y >= 0 && ( x + width <= p.width ) && ( y + height <= p.height ) )
    {
        if ( !list.reserve( width * height * p.channels ) )
            return false;

        int bottom = y + height, right = x + width;

        if ( !m_isLockExternal )
            doLock();

        extract();

        byte_t* bits = p.pixels;
        byte_t* pline = bits + y * p.rowStride;
        byte_t* sp = NULL;

        switch ( p.bitsPerPixel )
        {
        case 24:
            {
                for ( int j = y; j < bottom; j++ )
                {
                    sp = pline + x * p.channels;
                    for ( int i = x; i < right; i++ )
                    {
                        list.add( sp[ 0 ] );
                        list.add( sp[ 1 ] );
                        list.add( sp[ 2 ] );
                        //list.add( 0xFF );

                        sp += 3;
                    }
                    pline += p.rowStride;
                }
            }
            break;
        case 8:
            {
                for ( int j = y; j < bottom; j++ )
                {
                    sp = pline + x;
                    for ( int i = x; i < right; i++ )
                    {
                        list.add( *sp );
                        //list.add( c );
                        //list.add( c );
                        //list.add( 0xFF );

                        sp++;
                    }
                    pline += p.rowStride;
                }
            }
            break;

        case 1:

            for ( int j = y; j < bottom; j++ )
            {
                for ( int i = x; i < right; i++ )
                {
                    byte_t c = ::getBit( pline, i, p.paletteInverted ) == 0 ? 0xFF : 0;
                    list.add( c );
                    //list.add( c );
                    //list.add( c );
                    //list.add( 0xFF );
                }
                pline += p.rowStride;
            }

            break;
        }

        lighten();

        if ( !m_isLockExternal )
            unlock();
    }

    return true;
}

uint32_t _PluginModel::Image::getPixel( int x, int y )
{
    return 0;
}

void _PluginModel::Image::setPixel( int x, int y, uint32_t value )
{
    return;
}

void _PluginModel::Image::compress()
{
    //lock();

    store();
    lighten();

    unlock();
}
